package com.hospital.payment.module.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hospital.common.base.PageResult;
import com.hospital.common.base.Result;
import com.hospital.common.base.ThreadAttributes;
import com.hospital.common.dto.diagnosis.module.ChatStatusDTO;
import com.hospital.common.dto.payment.module.BackendOrderDto;
import com.hospital.common.dto.payment.module.OrderCreateDto;
import com.hospital.common.dto.payment.module.OrderDto;
import com.hospital.common.dto.payment.module.OrderPayDto;
import com.hospital.common.enums.ResultStatus;
import com.hospital.common.enums.RoleType;
import com.hospital.common.enums.diagnosis.module.SessionStatus;
import com.hospital.common.enums.payment.module.OrderStatus;
import com.hospital.common.enums.payment.module.OrderType;
import com.hospital.common.enums.payment.module.PaymentStatus;
import com.hospital.common.exception.GlobalException;
import com.hospital.common.pojo.TDiagnosisRecord;
import com.hospital.common.pojo.TOrder;
import com.hospital.common.pojo.TRegistration;
import com.hospital.common.pojo.TUser;
import com.hospital.common.utils.ThreadLocalUtils;
import com.hospital.common.vo.payment.module.BackendOrderVo;
import com.hospital.common.vo.payment.module.OrderStatVo;
import com.hospital.common.vo.payment.module.OrderVo;
import com.hospital.payment.module.exception.OrderServiceException;
import com.hospital.payment.module.exception.enums.OrderServiceExpCode;
import com.hospital.payment.module.feign.DiagnosisModuleFeign;
import com.hospital.payment.module.mapper.TOrderMapper;
import com.hospital.payment.module.message.OrderTimeoutMessage;
import com.hospital.payment.module.message.OrderTimeoutProducer;
import com.hospital.payment.module.service.TOrderService;
import com.hospital.payment.module.service.TRegistrationService;
import com.hospital.payment.module.service.TUserService;
import com.hospital.payment.module.utils.MinioUtils;
import com.hospital.payment.module.utils.OrderNumUtils;
import com.hospital.payment.module.utils.QRCodeGenerator;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.rocketmq.client.producer.SendStatus;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.io.ByteArrayInputStream;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

import static com.hospital.common.base.interfaces.Const.CONTENT_TYPE_PNG;

@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService{
    @Autowired
    private TOrderMapper tOrderMapper;
    @Autowired
    private TRegistrationService tRegistrationService;
    @Autowired
    private OrderNumUtils OrderNumUtil;
    @Autowired
    private MinioUtils minioUtils;
    @Autowired
    private QRCodeGenerator qrCodeGenerator;
    @Autowired
    private TUserService tUserService;
    @Autowired
    private OrderTimeoutProducer orderTimeoutProducer;
    @Autowired
    private DiagnosisModuleFeign diagnosisModuleFeign;

    private final double DISCOUNT_RATE = 0.1;
    @Override
    public PageResult<OrderVo> queryInPage(OrderDto orderDto) {
        checkEnums(orderDto);
        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        constructQueryWrapper(orderDto, queryWrapper);
        Page<TOrder> page = this.page(new Page<>(orderDto.getPageNum(), orderDto.getPageSize()), queryWrapper);
        return new PageResult<>(page.getTotal(), page.getRecords().stream().map(this::convert).toList());
    }
    private void checkEnums(OrderDto orderDto){
        if(!ObjectUtils.isEmpty(orderDto.getOrderType())){
            if(ObjectUtils.isEmpty(OrderType.valueOf(orderDto.getOrderType()))){
                throw new OrderServiceException(OrderServiceExpCode.ORDER_TYPE_NOT_EXIST);
            }
        }
        if(!ObjectUtils.isEmpty(orderDto.getOrderStatus())){
            if(ObjectUtils.isEmpty(OrderStatus.valueOf(orderDto.getOrderStatus()))){
                throw new OrderServiceException(OrderServiceExpCode.ORDER_STATUS_NOT_EXIST);
            }
        }
    }
    private OrderVo convert(TOrder order){
        OrderVo orderVo = new OrderVo();
        BeanUtils.copyProperties(order, orderVo);
        // 返回二维码url
        if(order.getOrderStatus().equals(OrderStatus.IN_PROGRESS.value)){
            if(order.getPaymentStat().equals(PaymentStatus.PENDING.value)){
                orderVo.setQrCodeUrl(minioUtils.getPresignedUrl(order.getQrcodeUrl()));
            }
        }
        return orderVo;
    }
    private void constructQueryWrapper(OrderDto orderDto, LambdaQueryWrapper<TOrder> queryWrapper){
        if(!ObjectUtils.isEmpty(orderDto.getPatientId())){
            queryWrapper.eq(TOrder::getPatientId, orderDto.getPatientId());
        }
        if(!ObjectUtils.isEmpty(orderDto.getOrderType())){
            queryWrapper.eq(TOrder::getOrderType, orderDto.getOrderType());
        }
        if(!ObjectUtils.isEmpty(orderDto.getOrderStatus())){
            queryWrapper.eq(TOrder::getOrderStatus, orderDto.getOrderStatus());
        }
        if(!ObjectUtils.isEmpty(orderDto.getStartTime())&&!ObjectUtils.isEmpty(orderDto.getEndTime())){
            queryWrapper.between(TOrder::getOrderCreateTime, orderDto.getStartTime(), orderDto.getEndTime());
        }
    }
    @Override
    public List<OrderVo> query(OrderDto orderDto) {
        checkEnums(orderDto);
        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        constructQueryWrapper(orderDto, queryWrapper);
        return tOrderMapper.selectList(queryWrapper).stream().map(this::convert).toList();
    }
    @GlobalTransactional(rollbackFor = GlobalException.class)
    @Override
    public void create(OrderCreateDto orderCreateDto) {
        OrderType orderType = OrderType.valueOf(orderCreateDto.getOrderType());
        if(ObjectUtils.isEmpty(orderType)){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_TYPE_NOT_EXIST);
        }
        TOrder order = new TOrder();
        initOrder(order, orderCreateDto);
        Boolean hasMedicalCard = tUserService.checkMedicalCard(orderCreateDto.getPatientId());
        ObjectMapper objectMapper = new ObjectMapper();
        OrderTimeoutMessage orderTimeoutMessage = null;
        switch (orderType){
            case DIAGNOSIS -> {
                TDiagnosisRecord record = objectMapper.convertValue(orderCreateDto.getBusinessData(), TDiagnosisRecord.class);
                order.setBusinessId(record.getId());
                order.setOrderAmount(record.getTotalCosts());
                order.setRemarks("药物费:"+record.getMedicationCosts()+"\n检查费:"+record.getConsultationFee());
                orderTimeoutMessage = OrderTimeoutMessage.builder().orderId(order.getOrderId()).orderType(orderType.value).build();
            }
            case REGISTRATION -> {
                TRegistration registration = objectMapper.convertValue(orderCreateDto.getBusinessData(), TRegistration.class);
                order.setBusinessId(registration.getId());
                order.setOrderAmount(registration.getRegistrationFee());
                orderTimeoutMessage = OrderTimeoutMessage.builder().orderId(order.getOrderId()).orderType(orderType.value).build();
            }
            case ONLINE_DIAGNOSIS -> {
                order.setOrderAmount(orderCreateDto.getOnlineCosts());
                order.setSessionId(orderCreateDto.getSessionId());
                order.setRemarks("线上咨询订单");
                orderTimeoutMessage = OrderTimeoutMessage.builder().orderId(order.getOrderId()).orderType(orderType.value).sessionId(order.getSessionId()).build();
            }
        }
        // 优惠判断
        if(hasMedicalCard){
            order.setDiscountAmount(order.getOrderAmount() * DISCOUNT_RATE);
        }else{
            order.setDiscountAmount(0.0);
        }
        order.setPayableAmount(order.getOrderAmount() - order.getDiscountAmount());
        // 创建二维码
        byte[] qrCodeBytes = null;
        try {
            qrCodeBytes = qrCodeGenerator.generateQRCodeBytes();
        } catch (Exception e) {
            throw new OrderServiceException(OrderServiceExpCode.QR_CODE_GENERATE_FAILED);
        }

        String objectName = minioUtils.uploadFile(order.getOrderId(), new ByteArrayInputStream(qrCodeBytes), qrCodeBytes.length, CONTENT_TYPE_PNG);
        order.setQrcodeUrl(objectName);
        if(tOrderMapper.insert(order)==0){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_CREATE_FAILED);
        }
        // 发送延迟消息
        orderTimeoutMessage.setThreadAttributes(ThreadLocalUtils.get());
        if(orderTimeoutProducer.sendOrderTimeoutMessage(orderTimeoutMessage).getSendStatus()!= SendStatus.SEND_OK){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_CREATE_FAILED);
        }
    }

    @Override
    @Transactional
    public void pay(OrderPayDto orderPayDto) {
        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TOrder::getOrderId, orderPayDto.getOrderId());
        TOrder order = tOrderMapper.selectOne(queryWrapper);
        if(ObjectUtils.isEmpty(order)){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_NOT_EXIST);
        }
        if(!Objects.equals(order.getPaymentStat(), PaymentStatus.PENDING.value)){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_PAYMENT_STATUS_NOT_PENDING);
        }
        order.setPaymentStat(PaymentStatus.SUCCESS.value);
        order.setOrderStatus(OrderStatus.COMPLETED.value);
        order.setPaymentMethodId(orderPayDto.getPaymentMethodId());
        order.setPaymentMethod(orderPayDto.getPaymentMethod());
        order.setPaymentTime(LocalDateTime.now());
        LambdaUpdateWrapper<TOrder> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(TOrder::getOrderId, order.getOrderId());
        if(tOrderMapper.update(order, updateWrapper)==0){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_UPDATE_FAILED);
        }
        // 如果为挂号订单则设置挂号订单状态
        if(order.getOrderType().equals(OrderType.REGISTRATION.value)){
            tRegistrationService.afterPay(order.getBusinessId());
        }
        // 如果为在线诊疗订单,需要更改订单状态
        if(order.getOrderType().equals(OrderType.ONLINE_DIAGNOSIS.value)){
            Result<?> result = diagnosisModuleFeign.updateChatStatus(ChatStatusDTO.builder().status(SessionStatus.IN_PROGRESS.value).sessionId(order.getSessionId()).build());
            if(!result.getCode().equals(ResultStatus.SUCCESS.getCode())){
                throw new OrderServiceException(OrderServiceExpCode.DIAGNOSIS_MODULE_ERROR);
            }
        }
        minioUtils.deleteFile(order.getQrcodeUrl());
    }

    @Override
    @Transactional
    public void cancelRegistration(Long patientId, Long registrationId) {
        LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TOrder::getPatientId, patientId)
                .eq(TOrder::getBusinessId, registrationId);
        TOrder order = tOrderMapper.selectOne(queryWrapper);
        if(ObjectUtils.isEmpty(order)){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_NOT_EXIST);
        }
        if(!Objects.equals(order.getOrderStatus(), OrderStatus.IN_PROGRESS.value)){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_STATUS_NOT_IN_PROGRESS);
        }
        if(!Objects.equals(order.getPaymentStat(), PaymentStatus.PENDING.value)){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_PAYMENT_STATUS_NOT_PENDING);
        }
        order.setOrderStatus(OrderStatus.CANCELED.value);
        LambdaUpdateWrapper<TOrder> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(TOrder::getOrderId, order.getOrderId());
        if(tOrderMapper.update(order, updateWrapper)==0){
            throw new OrderServiceException(OrderServiceExpCode.ORDER_UPDATE_FAILED);
        }
        tRegistrationService.cancelRegistration(registrationId);
        minioUtils.deleteFile(order.getQrcodeUrl());
    }

    @Override
    public OrderStatVo patientOrderStat() {
        ThreadAttributes threadAttributes = ThreadLocalUtils.get();
        if(ObjectUtils.isEmpty(threadAttributes)){
            throw new OrderServiceException(OrderServiceExpCode.USER_NOT_EXIST);
        }
        if(!threadAttributes.getRoleType().equals(RoleType.PATIENT.value)){
            throw new OrderServiceException(OrderServiceExpCode.USER_ROLE_NOT_MATCH);
        }
        return tOrderMapper.patientOrderStat(threadAttributes.getUserId());
    }

    @Override
    public OrderStatVo backendOrderStat() {
        return tOrderMapper.backendOrderStat();
    }

    @Override
    public PageResult<BackendOrderVo> backendQueryInPage(BackendOrderDto backendOrderDto) {
        LambdaQueryWrapper<TUser> queryWrapper = new LambdaQueryWrapper<>();
        TUser user = null;
        if(!ObjectUtils.isEmpty(backendOrderDto.getAccount())){
            queryWrapper.eq(TUser::getAccount, backendOrderDto.getAccount());
            user = tUserService.getOne(queryWrapper);
            if(ObjectUtils.isEmpty(user)){
                throw new OrderServiceException(OrderServiceExpCode.USER_NOT_EXIST);
            }
        }
        LambdaQueryWrapper<TOrder> orderQueryWrapper = new LambdaQueryWrapper<>();
        constructQueryWrapper(backendOrderDto, orderQueryWrapper);
        if(!ObjectUtils.isEmpty(user)){
            orderQueryWrapper.eq(TOrder::getPatientId, user.getId());
        }
        Page<TOrder> page = page(new Page<>(backendOrderDto.getPageNum(), backendOrderDto.getPageSize()), orderQueryWrapper);
        return new PageResult<>(page.getTotal(), page.getRecords().stream().map(this::convertToBackendOrderVo).toList());
    }
    private BackendOrderVo convertToBackendOrderVo(TOrder order){
        BackendOrderVo backendOrderVo = new BackendOrderVo();
        BeanUtils.copyProperties(order, backendOrderVo);
        TUser user = tUserService.getById(order.getPatientId());
        if(!ObjectUtils.isEmpty(user)){
            backendOrderVo.setName(user.getName());
            backendOrderVo.setPhone(user.getPhone());
            backendOrderVo.setSex(user.getSex());
            backendOrderVo.setAccount(user.getAccount());
        }
        return backendOrderVo;
    }
    private void constructQueryWrapper(BackendOrderDto backendOrderDto, LambdaQueryWrapper<TOrder> queryWrapper){
        if(!ObjectUtils.isEmpty(backendOrderDto.getOrderType())){
            queryWrapper.eq(TOrder::getOrderType, backendOrderDto.getOrderType());
        }
        if(!ObjectUtils.isEmpty(backendOrderDto.getOrderStatus())){
            queryWrapper.eq(TOrder::getOrderStatus, backendOrderDto.getOrderStatus());
        }
        if(!ObjectUtils.isEmpty(backendOrderDto.getPaymentStatus())){
            queryWrapper.eq(TOrder::getPaymentStat, backendOrderDto.getPaymentStatus());
        }
    }

    private void initOrder(TOrder order, OrderCreateDto orderCreateDto){
        order.setOrderId(OrderNumUtil.generateOrderNumber());
        order.setPatientId(orderCreateDto.getPatientId());
        order.setOrderType(orderCreateDto.getOrderType());
        order.setPaymentStat(PaymentStatus.PENDING.value);
        order.setOrderCreateTime(LocalDateTime.now());
        order.setOrderStatus(OrderStatus.IN_PROGRESS.value);
    }
}
